package com.am.builder;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.am.cell.ICell;
import com.am.link.Axon;
import com.am.link.HippoCampus;
import com.am.link.IAMLink;
import com.am.output.IAMOutput;
import com.am.persistent.IAMPersistent;
import com.am.service.AxonFactory;
import com.am.service.HippoCampusFactory;
import com.am.service.ServiceFactory;
import com.am.words.ICharacter;
import com.am.words.IWord;

public class AMBuilder {

    private IAMPersistent ps;
    private Queue<ICell> firedCells;
    private Queue<ICell> linkableCells;
    private IAMOutput os;
    private AxonFactory axonFac;
    private HippoCampusFactory hippoFac;

    public AMBuilder(IAMPersistent ps) {
	this.ps = ps;
	this.firedCells = new LinkedList<ICell>();
	this.linkableCells = new LinkedList<ICell>();
	this.os = ServiceFactory.getInstance().getOutputService();
	this.hippoFac = ServiceFactory.getInstance().getHippoCampusFactory();
	this.axonFac = ServiceFactory.getInstance().getAxonFactory();
    }

    public void setOutputService(IAMOutput os){
	    this.os = os;
    }

    public void buildWithWord(IWord s) {
	// get each character from the words
	List<ICharacter> characters = s.getCharacters();
	boolean process = true;
	int index = 0;
	while (process) {
	    ICharacter character = null;
	    if (index < characters.size()) {
		character = characters.get(index++);
		os.msg("Get input:" + character.getValue());
	    }
	    // check each firedCells
	    List<ICell> tFiredCells = new ArrayList<ICell>();
	    while (!firedCells.isEmpty()) {
		ICell fc = firedCells.poll();
		if (fc != null) {
		    os.msg("Cell fired in last runner:"
			    + fc.getValue().getValue());
		    // distribute voltage to all its Links
		    List<Axon> axons = fc.getAxons();
		    for (Axon axon : axons) {
			ICell c = allocateVoltage(axon, fc
				.getDistributableVoltage());
			if (c != null) {
			    os.sendOutput(c);
			    tFiredCells.add(c);
			}
		    }
		    // distribute voltage to all its Links
		    List<HippoCampus> hippoCampuses = fc.getHippoCampuses();
		    for (HippoCampus hc : hippoCampuses) {
			ICell c = allocateVoltage(hc, fc
				.getDistributableVoltage());
			if (c != null) {
			    os.sendOutput(c);
			    tFiredCells.add(c);
			}
		    }

		    // add it to linkable list
		    // check whether it's in linkable cells first, if so,
		    // replace it
		    if (linkableCells.contains(fc)) {
			linkableCells.remove(fc);
		    }
		    linkableCells.add(fc);// actually we update the cell with
		    // new state
		}
	    }

	    // add temp fired cells to firedCells
	    firedCells.addAll(tFiredCells);

	    // FIXME DO WE HAVE BETTER WAYS TO DO THIS?
	    // check each linkable cells
	    // get a copy of linkableCells
	    List<ICell> cpLinkable1 = new ArrayList<ICell>();
	    List<ICell> cpLinkable2 = new ArrayList<ICell>();
	    while (!linkableCells.isEmpty()) {
		ICell lc = linkableCells.poll();
		if (lc.isLinkable()) {
		    os.msg("One of the linkable cell:"
			    + lc.getValue().getValue());
		    cpLinkable1.add(lc);
		    cpLinkable2.add(lc);
		}
	    }

	    for (int i = 0; i < cpLinkable1.size(); i++) {
		ICell c1 = cpLinkable1.get(i);
		// link it with all others
		for (int j = i + 1; j < cpLinkable2.size(); j++) {
		    ICell c2 = cpLinkable2.get(j);
		    // WE CREATE HIPPOCAMPUS NOW
		    HippoCampus link = hippoFac.createHippoCampusLink(c1, c2);
		    os.msg("A link from " + c1.getValue().getValue() + " to "
			    + c2.getValue().getValue() + ":"
			    + link.getResistance());
		}
	    }

	    // add all linkable back
	    linkableCells.addAll(cpLinkable1);

	    if (firedCells.isEmpty() && linkableCells.isEmpty()) {
		os.msg("Fired and linkable cells Process End");
		process = false;
	    }

	    if (character != null) {
		process = true;
		ICell cell = ps.findCellByValue(character);
		if (cell == null) {
		    // we create a new Cell, and this cell is fired
		    // automatically
		    cell = ps.createCell(character);
		}
		// make it linkable
		cell.reLinkable();
		// put it into fired Cells
		firedCells.add(cell);
	    }
	}

	ps.dumpAllCells();
    }

    private ICell allocateVoltage(IAMLink link, int voltage) {
	ICell des = link.getDestination();
	int desRes = des.getResistance();
	int linkRes = link.getResistance();
	// series circuit
	if (des.addVoltage((desRes / (desRes + linkRes)) * voltage)) {
	    return des;
	} else {
	    return null;
	}

    }

}
